package com.ymj.netty.client.proxy;

import com.ymj.netty.annotation.RemoteInvoke;
import com.ymj.netty.client.TcpClient;
import com.ymj.netty.common.ClientRequest;
import com.ymj.netty.common.Response;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *  @Description: 动态代理，当我们去调用注解了@RemoteInvoke的成员的方法时，实际上我们是需要远程调用的
 *                          本类的作用即是在每次调用这些方法时，都进行代理，通过nettyClient去请求服务端获取response
 *  @author: yanmj
 *  @Date: 2020-06-07
 */
@Component
public class InvokeProxy implements BeanPostProcessor {

    //所有的<远程调用方法，其所属的类>
    final Map<Method,Class> methodClassMap = new ConcurrentHashMap<>();


    /**
     * bean初始化之前进行操作，遍历bean中的所有成员，如果有@RemoteInvoke，说明是远程调用，就需要进行代理
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields){
            //如果这个field有@RemoteInvoke注解
            if(field.isAnnotationPresent(RemoteInvoke.class)){
                field.setAccessible(true);
                //将所有的方法所属的接口放到一个map
                putMethodClass(methodClassMap,field);
                Enhancer enhancer = new Enhancer();
                enhancer.setInterfaces(new Class[]{field.getType()});  //例：UserApi
                //当consumer调用到接口中的方法时，就调用这个方法进行代理
                enhancer.setCallback(new MethodInterceptor() {
                    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy){
                        //通过netty客户端去远程调用服务端
                        ClientRequest request = new ClientRequest();
                        String className = methodClassMap.get(method).getName();
                        request.setCommand(className+"."+method.getName()); //com.ymj.server.controller.UserController.save
                        request.setContent(args[0]);//此处为了方便我只处理有一个参数的方法
                        Response response = TcpClient.send(request);
                        if(response == null){
                            return null;
                        }
                        return response.getContent();
                    }
                });
                try {
                    field.set(bean,enhancer.create());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }

    /**
     * bean初始化之后执行
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * 将所有的方法所属的接口放到一个map，这样我们就可以不用每次调用都自己手写方法全路径了
     * @param methodClassMap
     * @param field 注解RemoteInvoke的成员变量
     */
    private void putMethodClass(Map<Method, Class> methodClassMap, Field field) {
        Method[] methods = field.getType().getDeclaredMethods();
        for(Method method: methods){
            //<save,com.ymj.api.UserApi>
            methodClassMap.put(method,field.getType());
        }
    }
}
